Repository navigation
[OMNIML-6062] Dequantize block-FP8 checkpoints as they load expert-parallel - #2743
shengliangxu wants to merge 25 commits into
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review: 3
Please check the settings in the CodeRabbit UI or the
|
| Layer / File(s) | Summary |
|---|---|
FP8 dequantization and validation modelopt/torch/utils/plugins/model_load_utils.py, tests/unit/torch/utils/test_fp8_dequant_patch.py |
Adds dequantization support for partial and empty scale grids on Transformers versions before 5.10.1. Tests compare varied block shapes and output dtypes with reference results, and verify that newer versions bypass the patch. |
Expert conversion and patch lifecycle modelopt/torch/utils/plugins/model_load_utils.py, tests/unit/torch/utils/test_fp8_dequant_patch.py |
Combines FP8 dequantization with expert merging to preserve per-expert indexing. Tests cover converter ordering, repeated patching, hook wrapping, and restoration. |
FP8 loader configuration modelopt/torch/utils/plugins/model_load_utils.py |
Configures FP8 checkpoint loading with FineGrainedFP8Config(dequantize=True), applies the patch with the model dtype, and logs the selection. The MXFP4 configuration remains the alternative branch. |
Priority:
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Feature
Sequence Diagram(s)
sequenceDiagram
participant load_and_prepare_ep
participant from_pretrained
participant CompatibilityPatch
load_and_prepare_ep->>from_pretrained: load FP8 checkpoint with FineGrainedFP8Config(dequantize=True)
load_and_prepare_ep->>CompatibilityPatch: apply patch with model dtype
Merge Risk: 
Moderate · previous review 78b38
-
Loading block-FP8 checkpoints with older Transformers versions can silently produce incorrectly scaled weights in edge cases. Those weights would then be used for calibration and export. Before merging, make the patch reject scale grids that do not match their weights, or document why the current fallbacks are valid.
Pre-merge checks: 
5
-
Full details: Security Anti-Patterns
-
Explanation
No listed security anti-pattern was introduced. The PR changes only
modelopt/torch/utils/plugins/model_load_utils.pyand a unit test; no modelopt/example dependency manifests changed. The added code contains notorch.load(..., weights_only=False),numpy.load(..., allow_pickle=True), hardcodedtrust_remote_code=True, external-inputeval()/exec(), or# noseccomment. Existingtrust_remote_codehandling remains caller-configurable with a default ofFalse.
Full details: Title check
-
Explanation
The title names the main change: block-FP8 checkpoints are dequantized as they load under expert-parallel loading. The raw summary and PR objectives both center on the FP8 dequantization patch in load_and_prepare_ep, which matches the title. The title is specific, uses the ticket tag, and is a single sentence of 8 words.
-
✨ Finishing Touches: 💡 2
-
📝 Generate docstrings: 💡
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
🛠️ Fix failing CI checks: 💡
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## shengliangx/hfep-6b-ep-dense-sharding #2743 +/- ##
=========================================================================
+ Coverage 61.83% 61.86% +0.03%
=========================================================================
Files 649 649
Lines 72969 73059 +90
=========================================================================
+ Hits 45117 45195 +78
- Misses 27852 27864 +12
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
6eab845 to
d6cb219
Compare
f354b3b to
ed1ebbf
Compare
|
|
/ok to test ed1ebbf |
d6cb219 to
a5aecef
Compare
ed1ebbf to
6db2eb0
Compare
|
/ok to test 6db2eb0 |
a5aecef to
e8384c3
Compare
6db2eb0 to
6bcb1ca
Compare
e8384c3 to
ffbb850
Compare
6bcb1ca to
14f1610
Compare
|
/ok to test 14f1610 |
14f1610 to
f647855
Compare
|
/ok to test f647855 |
f647855 to
a40c380
Compare
a40c380 to
d407931
Compare
|
/ok to test b62a4c8 |
|
/ok to test 2d8cc5d |
A native block-FP8 checkpoint (e.g. DeepSeek-V3) is dequantized to bf16 while it loads, through transformers' FineGrainedFP8Config, since quantizers attach to bf16 weights. Its dequant is corrected on transformers < 5.10.1 for weights whose last 128x128 block is partial, and made to emit the model's dtype. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…out the session Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The patch and its module flags sit in model_load_utils with the expert-parallel loader that applies them, so the test drives and resets them there. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The patch fuses the Fp8Dequantize that transformers prepends to the routed-expert converter into its MergeModulelist, so the loader still gives each expert its tensor_idx. Only the install was tested. The new test runs the patched update_weight_conversions on an expert converter and the resulting chain on per-expert block-FP8 weights. It fails if the converter no longer leads with a MergeModulelist, or if the fused op skips the dequant or the merge. transformers 5.18's chain passes an output_dtype the patched dequant does not take, so the chain runs that version's own dequant; the patched arithmetic has its own tests. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
- The two block-size cases, a grid finer than the configured block and no block configured, become rows of the blockwise-reference table, which gains configured and grid columns. - The dtype test keeps its unrequested (bf16) and requested (fp16) cases; requesting bf16 or fp32 took the same branch. - The version test keeps the 5.10.1 boundary; 5.17.0 took the same branch. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The pre-5.10.1 dequant patch emitted a non-empty weight unscaled when its scale grid was empty. That only happens when a weight and its scales were sliced apart, as transformers 5.9's expert tensor_idx bug did on expert-parallel loads, so it loaded wrong weights without a word; upstream's own dequant fails on such a grid. It also fell back to the configured block for a grid neither that block nor an even split gives, scaling rows wrongly. Raise in both cases; an empty weight still passes through its empty grid. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
From 5.10.1 the patch kept transformers' own dequant untouched, on the assumption that no shipping model has a partial last block there. DeepSeek-V3, V3.1 and Kimi-K2-Instruct do: kv_a_proj_with_mqa has 576 rows under a 5-row grid of 128-row blocks, and Fp8Dequantize still raises "not divisible by scale grid" for it, so their block-FP8 loads failed. The stock dequant is kept, for its ue8m0 and FP4 decoding and output_dtype, and wrapped: an FP8 weight whose grid is the configured block's ceil grid is padded to whole blocks for it and cropped back. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
When fusing transformers' prepended expert dequant into its merge failed, the patch printed a warning and went on. Every routed expert then loaded sliced apart from its scales. The failure now raises. A transformers without the update_weight_conversions hook prepends no dequant, so there is nothing to fuse and the patch skips it. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…tizes The load dequantizes native block-FP8 checkpoints as it loads, so they join the formats it accepts. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…heck The hub-ID test now loads an unquantized, a block-FP8 and an MXFP4 config: each must reach the checkpoint's resolution, past the format check, so dropping either format from the accepted set fails it. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
From transformers 5.10.1 the expert-parallel load wraps transformers' block-FP8 dequant, which derives the block size from the scale grid, and padded a weight to whole blocks only when it did not divide into that grid. A partial last block can still divide evenly: 448 rows under a 4-row grid of 128-row blocks go to the dequant as blocks of 112, and every row group after the first gets the wrong scale, silently. The wrapper now pads whenever the weight is not block-aligned, which is a no-op for whole blocks. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The pre-5.10.1 dequant multiplies its own fp32 copy of the weight by the tiled scales in place, so a weight holds two full-size fp32 temporaries instead of three while it loads. The dequant is rebound after the expert converter's fuse, so a refused patch leaves transformers as it was. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The rule that a block-FP8 checkpoint's dense part is not sharded lands here, with the load that makes such a checkpoint loadable, rather than in [10/21], where it preempted the format refusal. Left to decide, the plan replicates that dense part and says why; asked to shard it, the decision refuses, naming MODELOPT_FSDP_SIZE=1, and the loader refuses a plan that shards it. The pre-5.10.1 dequant scales its fp32 copy of a weight in place, through a view of its whole column blocks and then the partial last one, against scales tiled over the rows only. It builds no full-size fp32 scale tile: an (18432, 7168) weight peaks at 6 bytes per element instead of 8. A weight narrower than one block joins the reference table. The expert converter's fuse no longer skips a merge it already fused, a layout transformers' update_weight_conversions never produces (it builds fresh converters on every call); fusing it again would dequantize once all the same. The load's log names the dtype the dequant emits. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
quant_method "fp8" is not block FP8 alone: per-tensor FP8 checkpoints (AutoFP8's: a scalar weight_scale per weight, no weight_block_size) name the same method. The expert-parallel load admitted them, and transformers' dequant reads them as 128x128 blocks with no weight_scale_inv, so their weights load unscaled without an error (on transformers 5.18 a tiny Llama loaded that way has max |w| 448 where the original has 0.079). _ckpt_quant_method now calls an fp8 checkpoint without a weight_block_size "fp8-per-tensor", which the load refuses, before anything loads, as a format it cannot dequantize; the decision's block-FP8 rule no longer applies to it either. The decision, the loader's refusal and its docstring give the reason a block-FP8 dense part cannot be sharded as Distributed Checkpoint reading it as stored, with no dequantization: its scales' format was never the blocker (DeepSeek-V3's are fp32). A comment says where the dequant's block size comes from: transformers keeps the checkpoint's quantization_config and takes only the loading attributes from the one the load passes. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
A config the load is handed can name its dtype, or carry "auto" for the checkpoint's own, and the dequant's Tensor.to reads a string as a device. The dtype a string names is now taken as the torch dtype, and "auto" as the bfloat16 default the dequant emits unrequested. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…tize Every transformers the repo supports ships Fp8Dequantize (5.5 does), so the skip never fires, and the load dereferences the class on the same releases. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The override installs below transformers 5.10.1 and took exactly (quantized, scales): 5.8.0 to 5.10.0 call it that way, and 5.10.1, where the gate switches to transformers' own dequant, is the first to pass output_dtype. A caller passing it would have stopped the load with a bare TypeError. It now takes output_dtype and, when given, emits it: the loader passes the destination dtype, which the module-level model dtype only reconstructs. The expert-converter test runs the patched dequant through this transformers' chain, which passes output_dtype, instead of restoring the stock one around it. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…uant From transformers 5.10.1 a partial last block is padded to whole blocks for transformers' own dequant. The padding cast the weight to fp32 first, so the dequant got an input dtype it never sees from the loader, and the load an fp32 copy it does not need. The weight is now padded through a same-size uint8 view, in its own fp8 dtype; the zero byte is zero in every fp8 format. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
From transformers 5.10.1 the patch wraps transformers' dequant, calling the method it replaces, and only this module's flag kept it from wrapping again. Re-importing the module loses that flag, so the wrapper wrapped itself, one frame per re-import. The dequant op now carries a flag of its own, as the FP8 quantizer does for its converter hook. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The expert-parallel load passes FineGrainedFP8Config(dequantize=True), and both dequant branches read weight_block_size off the quantizer's config, relying on transformers to keep the checkpoint's quantization_config and take only the loading attributes from the one passed. A test now pins that merge on whichever transformers runs the suite. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Its reason, that Distributed Checkpoint reads block FP8 as stored, holds at any EP degree, but its remedy, replicating the dense part to dequantize it as it loads, holds only where the expert-parallel load runs. Without expert parallelism the FSDP2 load refuses a quantized checkpoint itself. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…ckpoint An fp8 checkpoint without weight_block_size was told the load "cannot dequantize a 'fp8-per-tensor' checkpoint" while listing fp8 among what it dequantizes, and the refusal never named the block size that tells the two apart. It now says the load dequantizes block-fp8 checkpoints, which name their block in quantization_config.weight_block_size, and that this one names none. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
A per-tensor FP8 checkpoint names no weight_block_size, which is how the load tells it from block FP8 and refuses it. transformers' FP8 config object fills in a 128x128 block whatever the checkpoint names, so a per-tensor checkpoint whose config came in as that object, as a loaded model's does, read as block FP8 and would load unscaled. For an FP8 config object, the block now comes from the checkpoint's own config.json, where both the decision and the load can read it. The plan decision also stops offering an EP degree for a block-fp8 dense part sharded within several expert-data-parallel replicas: no EP degree lets that dense part shard. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Both patches unpacked the last two dims first, so a weight or scale grid of fewer than two dims failed with a bare unpack error from inside the patched method. From 5.10.1 such a call now goes to transformers' own dequant, as every case the patch does not place does; below it, the override refuses it by name. The override also drops its FP4 branch: it called an unpack transformers only added in 5.10.1, so it could not run on the releases the override replaces. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The per-tensor check reads an FP8 config object's block from the checkpoint's own config.json, but a path without one, a Hub ID the load has not made local yet, left the object's default 128x128 block standing, and a per-tensor checkpoint would still load unscaled. Such a config is now "fp8-unverified", which the load refuses, naming the two ways in: the config as AutoConfig.from_pretrained reads it, or a local checkpoint. Reading the file decides it on every rank alike, without fetching anything. For a block-fp8 dense part within several expert-data-parallel replicas, the remedy also names starting from an unquantized copy, which on the warning path is the one that changes the outcome. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
|
/ok to test a460975 |
|
/claude review |
There was a problem hiding this comment.
Claude review — 0 CRITICAL, 0 IMPORTANT, 0 new SUGGESTION
Full review (the trigger comment carried no scoping instructions). 5 files changed (803+/46−). I read modelopt/torch/utils/plugins/model_load_utils.py around every changed hunk plus _decide_distribution whole, the resolve_distribution_plan signature/docstring, load_and_prepare_ep's signature and its refusal block at :2438-:2466, and all four test diffs.
State of the change
HEAD is a4609752 ("Refuse an FP8 config object whose checkpoint config cannot be read") — the round-5 revision, and the metadata matches byte-for-byte what the last round approved. Nothing has moved but the base. I did not take the prior rounds' verification on trust; the re-derivation below is independent.
Re-derived this round
- Pre-5.10.1 block arithmetic holds on every boundary I could construct.
whole = cols // block_ncan never exceedscale_cols: the ceil-at-config branch of_blkgivesfloor(cols/block) <= ceil(cols/block), the exact-division branch gives equality — sos[..., :whole, None]is always in range and the tail is 0 or 1 scale columns, which broadcasts against a remainder narrower thanblock_n.whole == 0(a weight narrower than one block) unflattens a zero-width slice, which is legal, and the tailmul_then covers the whole weight.scale_rows * block_m >= rowsin both branches, so[..., :rows, :]never under-fills the row tile.q[..., :whole*block_n]is basic slicing andunflatten(-1, (whole, block_n))on strides(…, cols, 1)is view-expressible, so themul_really is in place onq's owncopy=Truebuffer — the checkpoint tensor is untouched, and the PR's measured 6.0 B/elem matchesq(fp32) + the bf16 result with no full-size fp32 tile. - The
>= 5.10.1bypass condition is still the exact one under which stock recovers the configured block. Padding fires only whenscales.shape[-2:] == ceil_gridand the weight is not block-aligned; after the pad, upstream'spadded_rows // scale_rowsisblock[0]by construction. Gating on grid divisibility instead — the shape this had in an earlier round — is what the(192, 256)/(2, 2)case pins against. The pad is done in the weight's own fp8 dtype through an equal-itemsizeuint8view, whereF.pad's constant zero is also fp8 zero, andfp8_dtypesexcludes the FP4 dtypes so upstream's unpack and ue8m0 decoding are never routed through it. - Refusal ordering and reachability.
_ckpt_quant_methodhas exactly two call sites (:1470,:2438), so neither"fp8-per-tensor"nor"fp8-unverified"can reach a stale== "fp8"comparison; the fourquant_methodchecks at:2439/:2444/:2452/:2459fire in the right order, each with a distinct and actionable message, and all of them precede_resolve_checkpoint_dir— a refused Hub-ID load downloads nothing.block_fp8deliberately does not match the two new values, so such a plan can still resolve todense="fsdp"; harmless, since the format refusal is reached either way before a weight is read. _decide_distributioncomposition.block_fp8is read afterep_sizeis resolved from"auto", and from the deepcopy thatbuild_meta_causal_lmhas already written to —_ckpt_quant_methodhandlesquantization_configas dict or object, so a dict→object normalization during the build cannot flip the answer. Theelifarm setsunshardable/remedythat onlydense == "auto"(warn + replicate) ordense == "fsdp"(raise) consume, so an explicit"replicate"is a no-op.MODELOPT_FSDP_SIZE=1really does resolve todense="replicate", and the new test row (8 experts, world 4 →ep_size == 4,edp == 1) lands on the fp8elifrather than the edp arm, so it pins the branch it means to.- No subfolder hole.
load_and_prepare_eptakes nosubfolder, so_config_json_quantization(ckpt_path)readingckpt_path/config.jsonis consistent with what that call site can be handed; at the_decide_distributionsiteresolve_distribution_planhas already resolved the snapshot and joinedsubfolder. Its failure modes are all caught —FileNotFoundError/NotADirectoryErrorunderOSError,JSONDecodeErrorunderValueError. - Patch hygiene, re-imports, collectives. Both install flags live on the transformers classes, not the module, so a re-imported
model_load_utilscannot double-wrap; the pre-5.10.1 override is a replacement rather than a wrapper, so a rebind after a lost module flag is idempotent; the dequant rebind sits after the converter fuse, so a refused fuse leaves transformers stock (asserted over all three patch targets); and the fuse collapses[Fp8Dequantize, _DequantThenMerge, Concatenate]back to one op instead of skipping and leavingoperations[0]anFp8Dequantize. Every refusal is config-derived and so raises identically on every rank; theis_master()print is the only rank-dependent statement and issues no collective. No mode registration,modelopt_stateschema, public__init__export or export path is touched, andtransformers/packagingimports are all function-local. - Dtype normalization.
_patch_finegrained_fp8_dequantresolves a string dtype (and"auto"→ bf16) before it can reachTensor.to, which would otherwise parse it as a device;output_dtypetakes precedence over the module global, which is what keeps two sequential loads at different dtypes from borrowing each other's.
Findings
No new CRITICAL or IMPORTANT issues. The two non-blocking suggestions from the previous round are still open, unchanged, and I am not re-filing them as new threads:
:2455— the block verified inconfig.jsonis not plumbed intoFineGrainedFP8Config(dequantize=True). Narrower than it reads: a caller-supplied config object with a non-defaultweight_block_sizesurvives transformers' merge, so this only bites a caller who hands a default-block object for a non-128 checkpoint.:2098—_config_json_quantizationreturns the firstquantization_configdict without checking itsquant_methodor which sub-config the caller was examining, so the per-tensor refusal text can describe AutoFP8 for a checkpoint naming another format. Refuses either way; only the message misleads.
Risk
Low. Additive, and reached only by an expert-parallel plan over a block-FP8 checkpoint, with no in-repo caller until #2754. On transformers >= 5.10.1 every weight but the partial-block ones goes to upstream unchanged; the pre-5.10.1 override is checked against an exact blockwise reference across 10 parametrized shapes with visible mutation-checking, and the expert-converter fuse is exercised on a real update_weight_conversions output rather than a mock. Test weight matches the repo's "be stingy" guidance — the shared reference table carries the load, the pre-existing _ckpt_quant_method and hub-ID cases were parametrized rather than duplicated, and the new tmp_path case is a three-row table over one file write. All CPU-only and fast, so tests/unit's 20-minute budget is unaffected. Approving.
🤖 Generated with Claude Code
Note
The expert-parallel PTQ series is now a tree instead of one stack (re-cut 2026-10-27, from main 1521053), so PRs that do not depend on each other can be reviewed and merged in parallel. This PR was [11/21]. Rebased onto main 7c3c868 later the same day, after #2739 merged, and onto main f03651e that night.
Base: #2742. The diff below is this PR's alone, and it merges after #2742.
Re-cut: this PR's diff is unchanged; only its base moved. Review fixes since are listed at the end of Testing.
Series layout
mainWhat does this PR do?
Type of change: New feature
A native block-FP8 checkpoint, such as DeepSeek-V3, is dequantized to bf16 while it loads expert-parallel, through transformers'
FineGrainedFP8Config(dequantize=True). Quantizers attach to bf16 weights.A block-FP8 checkpoint keeps its dense part replicated (moved here from [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742, where block FP8 did not load at all): DCP, which loads a sharded dense part, cannot dequantize it: it reads the checkpoint as stored. Left to decide, the plan ([OMNIML-6054] Decide how a model is distributed, from the model built on the meta device #2737) replicates it and says so; an explicit
fsdpis refused by the decision, namingMODELOPT_FSDP_SIZE=1, and by the loader.Per-tensor FP8 is refused. AutoFP8's per-tensor checkpoints name the same
quant_methodwithout aweight_block_size; transformers' dequant would read them as 128x128 blocks and load their weights unscaled, without an error. The load refuses them, before anything loads, asfp8-per-tensor, naming theweight_block_sizethey lack. A config handed in as transformers' FP8 config object, which fills in a 128x128 block, is read against the checkpoint's ownconfig.json, and refused when the path holds none to read (a Hub ID the load has not made local yet). A tiny per-tensor FP8 Llama, loaded that way on transformers 5.18:On transformers older than 5.10.1, the loader also patches that dequantization in three ways:
kv_a_proj_with_mqa: (576, 7168) with a (5, 56) grid. It also raises on an empty scale grid. The patch tiles each block's scale and crops to the weight, using the checkpoint'sweight_block_size.MergeModulelist. The loader then assigns no per-expert index, and the expert-parallel shard slices each expert's rows instead of whole experts: a DeepSeek-V3gate_up_projloads as (256, 32, 7168) and fails calibration. The patch fuses the dequantization into the merge, so the converter starts with aMergeModulelistagain and runs as upstream intends.The patch installs once and survives a re-imported module. From transformers 5.10.1 it keeps upstream's dequantization, which takes the destination dtype and decodes E8M0 scales, and only pads a weight with a partial last block to whole blocks for it, in its own fp8 dtype, since upstream still infers the block from the scale grid: (448, 7168) under a (4, 56) grid would otherwise be scaled in blocks of 112 rows.
The pre-5.10.1 dequantization scales its own fp32 copy of the weight in place, through a view of its whole column blocks, against scales tiled over the rows only, so no full-size fp32 tile is built. It is rebound only once the expert converter's fuse is in, so a refused patch leaves transformers as it was. Against scaling by a full-size fp32 tile, on CPU with one thread:
Usage
Automatic:
load_and_prepare_epreads the checkpoint'squantization_configand picks this path forquant_method: fp8with aweight_block_size.Testing
tests/unit/torch/utils/test_fp8_dequant_patch.py, on CPU:MergeModulelist, and per-expert block-FP8 weights come out as the blockwise reference. It fails without the fusion, and if the fused op skips the dequantization or the merge. Skipped on transformers 5.5, which has noupdate_weight_conversions.test_ep_dense_sharding.py); the decision replicates it, or refuses an explicitfsdp, namingMODELOPT_FSDP_SIZE=1(test_resolve_distribution_plan.py).fp8-per-tensor(test_ep_loader.py; both rows fail without the fix); the block-FP8 configs in the tests carry theirweight_block_size.tests/uniton transformers 5.18 and 5.5 and on torch 2.8, and the hf_ptq example tests, each with the same failures as main in that environment. CI runs the full unit suite.Fp8Dequantizeskip is gone. Each behaviour fix has a test that fails without it; pre-commit, mypy included, and this PR's own unit tests pass at this head.weight_block_size; a comment says why the block-fp8 dense rule applies under expert parallelism only; the per-tensor FP8 refusal names the missingweight_block_size. Each behaviour fix has a test that fails without it; pre-commit, mypy included, and this PR's own unit tests pass at this head.config.json; for a block-fp8 checkpoint, the several-replicas remedy names onlyMODELOPT_FSDP_SIZE=1; both dequant patches check a weight's dims before unpacking its shape, and the pre-5.10.1 override drops an FP4 branch it could not run. Each behaviour fix has a test that fails without it; pre-commit, mypy included, and this PR's own unit tests pass at this head.config.json(a Hub ID not yet made local) is refused rather than read as 128x128 blocks; for a block-fp8 dense part within several replicas the remedy also names starting from an unquantized copy. Each behaviour fix has a test that fails without it; pre-commit, mypy included, and this PR's own unit tests pass at this head.Before your PR is "Ready for review"
CONTRIBUTING.md: N/AAdditional Information
Part 11 of 21 of expert-parallel PTQ for Hugging Face MoE models; merges after #2742.
Summary by CodeRabbit